#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#include <linux/of.h>
#include <linux/of_address.h>

#include <linux/of_gpio.h>

struct dts_led
{
    int major;
    int minor;
    dev_t dev_id;                                                         //led设备号

    struct cdev st_chardev_led;                             //led字符设备，用于向内核添加字符设备
    struct class *st_class_chardev_led;             //led类，用于作为创建节点的参数
    struct device* st_device_chardev_led;       //led设备节点，用来在/dev下创建节点

    struct device_node *st_pinctrlled_node;         //led的设备树节点，用来获取设备树节点下的信息

    int gpio_num;
};

struct dts_led  st_pinctrlled;

#define CHARDEVLED_MAJOR       200
#define CHARDEVLED_NAME         "05_pinctrlled"

#define LED_ON      1
#define LED_OFF     0

int led_open(struct inode *nd, struct file *fp)
{
    fp->private_data = &st_pinctrlled;
    return 0;
}

ssize_t led_read(struct file *fp, char __user *buf, size_t len, loff_t *offset)
{
    return 0;
}


ssize_t led_write (struct file *fp, const char __user *buf, size_t len, loff_t *offset)
{

    int retval = 0;
    unsigned char recvbuf[1];
    unsigned char led_state;

    struct dts_led  *st_write  = fp->private_data;

    retval = copy_from_user(recvbuf,buf,len);
    if(retval < 0)
    {
        printk("kernel write failed\r\n");
        return -EFAULT;
    }

    led_state = recvbuf[0];

    if (led_state == LED_ON)        
    {
        //void gpio_set_value(unsigned gpio, int value)
        gpio_set_value(st_write->gpio_num,0);
    }
    else if (led_state == LED_OFF)
    {
        gpio_set_value(st_write->gpio_num,1);
    }

    return 0;
}

int led_release (struct inode *nd, struct file *fp)
{
    return 0;
}

struct file_operations  led_fops =  
{
        .owner = THIS_MODULE,
        .open = led_open,
        .read = led_read,
        .write = led_write,
        .release = led_release,
};

static int __init newchardevled_init(void)
{
    struct property *st_led_property;
    int ret_val;
    const char *str;

    //static inline struct device_node *of_find_node_by_path(const char *path)
    st_pinctrlled.st_pinctrlled_node = of_find_node_by_path("/lux_ledgpio");
    if (st_pinctrlled.st_pinctrlled_node == NULL)           
    {
        printk("node:lux_ledgpio not found\r\n");
        return -EINVAL;
    }
    else
    {
        printk("node:lux_ledgpio found\r\n");
    }

    /*
    struct property *of_find_property(const struct device_node *np,
						const char *name,
						int *lenp)
    */
    st_led_property = of_find_property(st_pinctrlled.st_pinctrlled_node,"compatible",NULL);
    if (st_led_property == NULL)           
    {
        printk("st_led_property not found\r\n");
    }
    else
    {
        printk("st_led_property found:%s\r\n",(char *)st_led_property->value);
    }

    /*
    int of_property_read_string(struct device_node *np,
					  const char *propname,
					  const char **out_string)
    */
    ret_val = of_property_read_string(st_pinctrlled.st_pinctrlled_node,"status",&str);
    if (ret_val < 0)
    {
        printk("st_led_property_read_string failed\r\n");
    }
    else
    {
        printk("st_led_property_read_string :%s\r\n",str);        
    }
    
    /*
    int of_get_named_gpio(struct device_node *np,
                                   const char *propname, int index)
    */
    st_pinctrlled.gpio_num = of_get_named_gpio(st_pinctrlled.st_pinctrlled_node,"led-gpio",0);
    if (st_pinctrlled.gpio_num < 0)
    {
        printk("not find led-gpio\r\n");
    }
    else
    {
        printk("led-gpio:%d\r\n",st_pinctrlled.gpio_num);
    }

    ret_val = gpio_direction_output(st_pinctrlled.gpio_num,1);//设置为输出
    if(ret_val < 0)
    {
        printk("can not set gpio\r\n");
    }
    

    
    if(st_pinctrlled.major)
    {
        //int register_chrdev_region(dev_t, unsigned, const char *);
        
        st_pinctrlled.dev_id = MKDEV(st_pinctrlled.major,0);
        register_chrdev_region(st_pinctrlled.dev_id,1,CHARDEVLED_NAME);
    }
    else
    {
        //alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
        alloc_chrdev_region(&st_pinctrlled.dev_id,0,1,CHARDEVLED_NAME);
        st_pinctrlled.major = MAJOR(st_pinctrlled.dev_id);
        st_pinctrlled.minor = MINOR(st_pinctrlled.dev_id);
    }
    printk("major:%d\tminor:%d\r\n",st_pinctrlled.major,st_pinctrlled.minor);


    //通过结构体c_dev注册字符设备
    st_pinctrlled.st_chardev_led.owner = THIS_MODULE;
    //void cdev_init(struct cdev *, const struct file_operations *);
    cdev_init(&st_pinctrlled.st_chardev_led,&led_fops);
    //int cdev_add(struct cdev *, dev_t, unsigned);
    cdev_add(&st_pinctrlled.st_chardev_led,st_pinctrlled.dev_id,1);

    //创建设备节点
    //1.创建一个class
    //2.在class下面创建一个设备

    /*
    struct class * __must_check __class_create(struct module *owner,
						  const char *name,
						  struct lock_class_key *key);
    返回一个结构体指针
    */
   
    st_pinctrlled.st_class_chardev_led = class_create(THIS_MODULE,CHARDEVLED_NAME);
    if (IS_ERR(st_pinctrlled.st_class_chardev_led))
    {
        return PTR_ERR(st_pinctrlled.st_class_chardev_led);
    }
    
    /*
    struct device *device_create(struct class *cls, struct device *parent,
			     dev_t devt, void *drvdata,
			     const char *fmt, ...);
    返回一个结构体指针             
    */
   
    st_pinctrlled.st_device_chardev_led = device_create(st_pinctrlled.st_class_chardev_led,NULL,st_pinctrlled.dev_id,NULL,CHARDEVLED_NAME);
    if (IS_ERR(st_pinctrlled.st_device_chardev_led))
    {
        return PTR_ERR(st_pinctrlled.st_device_chardev_led);
    }
    
    return 0;
}


static void __exit newchardevled_exit(void)
{
    //void cdev_del(struct cdev *);
    cdev_del(&st_pinctrlled.st_chardev_led);
    //void unregister_chrdev_region(dev_t, unsigned);
    unregister_chrdev_region(st_pinctrlled.dev_id,1);

    device_destroy(st_pinctrlled.st_class_chardev_led,st_pinctrlled.dev_id);
    class_destroy(st_pinctrlled.st_class_chardev_led);
}

module_init(newchardevled_init);
module_exit(newchardevled_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("Lux");
